package DateTime::Cursor::TestScaffolding::Operators;

use warnings;
use strict;
use Carp;

use version; our $VERSION = qv('0.0.1');
use base qw( Exporter );
our @EXPORT = qw( calculate_number_of_addition_subtraction_tests run_addition_subtraction_tests );

use Test::More;
#use Test::More qw(no_plan);
use Data::Dumper;

use DateTime::Format::Mail;
use DateTime::TimeZone;

use DateTime::Cursor::TestScaffolding::Utilities qw (nop_tests pretty_print cmp_cursors str_cmp_dt_to_cursor num_cmp_dt_to_cursor );

use DateTime::Cursor;


my %inverse_operator_lookup = ( '+' => '-',
                        '++' => '--',
                        '+=' => '-=');

my $base_number_of_addition_subtraction_tests = 39;

sub calculate_number_of_addition_subtraction_tests {
  my $rh_args = $_[0];
  my $number_of_tests = $base_number_of_addition_subtraction_tests; 
  foreach my $key (keys %{$rh_args}) {
    my $num_factors = scalar @{$rh_args->{$key}};
    $number_of_tests *= $num_factors;
  }
  return $number_of_tests;
}

sub run_addition_subtraction_tests {
  my $rh_args = $_[0];
  confess "undefined args" unless defined $rh_args;

  my $seed_dt = DateTime->now(time_zone => 'UTC');
  foreach my $interval (@{$rh_args->{'intervals'}}) {
    foreach my $operator (@{$rh_args->{'operators'}}) {
      foreach my $tz (@{$rh_args->{'timezones'}}) {
        foreach my $formatter (@{$rh_args->{'formatters'}}) {
          foreach my $offset (@{$rh_args->{'offsets'}}) {
            if ('++' eq $operator) {
              # for ++ operator , we are overriding offset $offset, and using 1 instead
              _addition_subtraction_test_case($seed_dt, $operator, 1, $interval, $tz, $formatter);
            } else {
              _addition_subtraction_test_case($seed_dt, $operator, $offset, $interval, $tz, $formatter);
            }
          }
        }
      }
    }
  }
}

sub _calc_expected_cmp {
  my ($offset, $hint_cmp) = @_;
  my $expected_cmp = $hint_cmp;
  if ( $offset == 0 ) {
    $expected_cmp = 0;
  } elsif ($offset < 0) {
    $expected_cmp *= -1;  
  }
  # diag("calculated expected cmp with offset=$offset, hint_cmp=$hint_cmp : expected_cmp=$expected_cmp");
  return $expected_cmp;
}

sub _addition_subtraction_test_case {
  my ($seed_dt, $operator, $offset, $interval, $tz, $formatter) = @_;
  confess "unsupported operator $operator" unless ($operator =~ /(\+)|(\+\+)|(\+=)/go);
  my $inverse_operator = $inverse_operator_lookup{$operator};

  #
  # set baseline
  #
  my $cursor_control = DateTime::Cursor->new({pivot => $seed_dt->clone(), interval => $interval});
  my $cursor_test = DateTime::Cursor->new({pivot => $seed_dt->clone(), formatter => $formatter, timezone => $tz, interval => $interval}); 
  my $dt_control = $seed_dt->clone();
  $dt_control->set_time_zone($tz);
  
  cmp_cursors($cursor_control, $cursor_test, 0, "compare control cursor with umodified test cursor");
  my $msg = "compare control datetime with unmodified test cursor"; 
  str_cmp_dt_to_cursor($dt_control, $cursor_test, 0, $formatter, $msg);
  num_cmp_dt_to_cursor($dt_control, $cursor_test, 0, $msg);
  
  #
  # bump forward
  #
  my $cursor_operation_result = perform_operation($operator, $cursor_test, $offset,
                                                "perform $operator operation on test cursor : " . pretty_print($cursor_test));
  cmp_cursors($cursor_control, $cursor_test, 0, "compare control cursor with original test cursor");
  cmp_cursors($cursor_control, $cursor_operation_result, _calc_expected_cmp($offset, -1),
                "compare control cursor with resultant of $operator operation : " . pretty_print($cursor_operation_result));

  #
  # bump backward
  #
  my $cursor_inverse_operation_result = perform_operation($inverse_operator, $cursor_operation_result, $offset,
                                                        "perform $inverse_operator operation on resultant of $operator operation : " .
                                                        pretty_print($cursor_operation_result));
  cmp_cursors($cursor_control, $cursor_test, 0, "compare control cursor with original test cursor");
  cmp_cursors($cursor_control, $cursor_inverse_operation_result, 0,
                "compare control cursor with resultant of $inverse_operator operation on the resultant of the $operator operation : " .
       pretty_print($cursor_inverse_operation_result));
  $msg = "compare control datetime with resultant of $inverse_operator operation on the resultant of the $operator operation : " .
       pretty_print($cursor_inverse_operation_result);
  str_cmp_dt_to_cursor($dt_control, $cursor_inverse_operation_result, 0, $formatter, $msg);
  num_cmp_dt_to_cursor($dt_control, $cursor_inverse_operation_result, 0, $msg);
  
  #
  # bump backward again
  #
  my $cursor_other_inverse_operation_result = perform_operation($inverse_operator, $cursor_inverse_operation_result, $offset,
                                                              "perform $inverse_operator operation on resultant of $inverse_operator operation on the " .
       "resultant of the $operator operation : " . pretty_print($cursor_inverse_operation_result));
  cmp_cursors($cursor_control, $cursor_test, 0, "compare control cursor with original test cursor");
  cmp_cursors($cursor_control, $cursor_other_inverse_operation_result, _calc_expected_cmp($offset, 1),
                "compare control cursor with resultant of $inverse_operator operation on the resultant of the " .
       "$inverse_operator operation on the resultant of the $operator operation : " .
       pretty_print($cursor_other_inverse_operation_result));
  return;
}

sub perform_operation {
  my ($operator, $operand_1, $operand_2) = ( $_[0], $_[1], $_[2]);
  my $resultant;
  if ($operator eq '+') {
    $resultant = $operand_1 + $operand_2;
  } elsif ($operator eq '-') {
    $resultant = $operand_1 - $operand_2;
  } elsif ($operator eq '++') {
    $operand_1++;
    $resultant = $operand_1;
  } elsif ($operator eq '--') {
    $operand_1--;
    $resultant = $operand_1;
  } elsif ($operator eq '+=') {
    $operand_1 += $operand_2;
    $resultant = $operand_1;
  } elsif ($operator eq '-=') {
    $operand_1 -= $operand_2;
    $resultant = $operand_1;
  } else {
    confess "supported operator $operator was used"; 
  }
  return $resultant;
}

1;
